IOMMU VTD BUG: disable Extended Interrupt Mode when disabling Interupt
authorAndrew Cooper <andrew.cooper3@citrix.com>
Wed, 15 Jun 2011 15:07:45 +0000 (16:07 +0100)
committerAndrew Cooper <andrew.cooper3@citrix.com>
Wed, 15 Jun 2011 15:07:45 +0000 (16:07 +0100)
Remapping

Experimental evidence shows that Extended Interrupt Mode remains in
effect even after Interrupt Remapping is disabled in each DMAR Global
Command Register.  A consiquence of this is that when we switch from
x2apic mode back to xapic mode, and disable interrupt remapping for
the kdump kernel, interrupts passing through the IO APICs are in
x2apic format as opposed xapic.  This causes a triple fault in the
kexec kernel.

As EIM is explicitly set up each time Interrup Remapping is enabled,
it is safe for us to clobber this when taring down.

Also, change the header definition of IRTA_REG_EIME_SHIFT.  It caused
verbose and error-prone code, and was only used in 1 place before.  We
now have IRTA_EIME which is the specific bit in the register.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
xen/drivers/passthrough/vtd/intremap.c
xen/drivers/passthrough/vtd/iommu.h

index 2fbf89fb144fd90e0ea4b397ddb738ee4234e1ee..046a216619bfc7f75e26f0a00455b12f3384ac0d 100644 (file)
@@ -811,11 +811,22 @@ int enable_intremap(struct iommu *iommu, int eim)
 void disable_intremap(struct iommu *iommu)
 {
     u32 sts;
+    u64 irta;
     unsigned long flags;
 
     if ( !ecap_intr_remap(iommu->ecap) )
         return;
 
+    spin_lock_irqsave(&iommu->register_lock, flags);
+    sts = dmar_readl(iommu->reg, DMAR_GSTS_REG);
+    if ( !(sts & DMA_GSTS_IRES) )
+        goto out;
+
+    dmar_writel(iommu->reg, DMAR_GCMD_REG, sts & (~DMA_GCMD_IRE));
+
+    IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, dmar_readl,
+                  !(sts & DMA_GSTS_IRES), sts);
+
     /* If we are disabling Interrupt Remapping, make sure we dont stay in
      * Extended Interrupt Mode, as this is unaffected by the Interrupt 
      * Remapping flag in each DMAR Global Control Register.
@@ -823,24 +834,18 @@ void disable_intremap(struct iommu *iommu)
      * in x2apic mode.  Any code turning interrupt remapping back on will set
      * EIME back correctly.
      */
-    if ( iommu_supports_eim() )
-    {
-        u64 irta;
-        irta = dmar_readl(iommu->reg, DMAR_IRTA_REG);
-        dmar_writel(iommu->reg, DMAR_IRTA_REG, irta & ~IRTA_EIME);
-        IOMMU_WAIT_OP(iommu, DMAR_IRTA_REG, dmar_readl,
-                      !(irta & IRTA_EIME), irta);
-    }
+    if ( !ecap_eim(iommu->ecap) )
+        goto out;
 
-    spin_lock_irqsave(&iommu->register_lock, flags);
-    sts = dmar_readl(iommu->reg, DMAR_GSTS_REG);
-    if ( !(sts & DMA_GSTS_IRES) )
+    /* Can't read the register unless we ecaps says we can */
+    irta = dmar_readl(iommu->reg, DMAR_IRTA_REG);
+    if ( !(irta & IRTA_EIME) )
         goto out;
 
-    dmar_writel(iommu->reg, DMAR_GCMD_REG, sts & (~DMA_GCMD_IRE));
+    dmar_writel(iommu->reg, DMAR_IRTA_REG, irta & ~IRTA_EIME);
+    IOMMU_WAIT_OP(iommu, DMAR_IRTA_REG, dmar_readl,
+                  !(irta & IRTA_EIME), irta);
 
-    IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG, dmar_readl,
-                  !(sts & DMA_GSTS_IRES), sts);
 out:
     spin_unlock_irqrestore(&iommu->register_lock, flags);
 }
index cb70136953ecd394af063a010d1e74f8f7626d1e..e75b24fb67e6ab23936199c1a21e73ba82be7e14 100644 (file)
@@ -471,7 +471,7 @@ struct qinval_entry {
 
 #define IEC_GLOBAL_INVL         0
 #define IEC_INDEX_INVL          1
-#define IRTA_EIME               (1 << 11)
+#define IRTA_EIME               (((u64)1) << 11)
 
 /* 2^(IRTA_REG_TABLE_SIZE + 1) = IREMAP_ENTRY_NR */
 #define IRTA_REG_TABLE_SIZE     ( IREMAP_PAGE_ORDER + 7 )